package co.edu.unal.bioingenium.kbmed.knowledge.visualization;

import co.edu.unal.bioingenium.kbmed.knowledge.KnowledgeService;
import co.edu.unal.bioingenium.kbmed.knowledge.KnowledgeSourceMetaData;
import co.edu.unal.bioingenium.kbmed.knowledge.visualization.vo.NodeData;
import co.edu.unal.bioingenium.kbmed.knowledge.visualization.vo.SubHierarchy;
import co.edu.unal.bioingenium.kbmed.knowledge.visualization.vo.SubHierarchyNode;
import co.edu.unal.bioingenium.kbmed.retrieval.data.RetrievalData;
import co.edu.unal.bioingenium.kbmed.retrieval.vo.RelevantDocument;
import co.edu.unal.bioingenium.kbmed.text.index.structures.InvertedIndexStrToStr;
import co.edu.unal.bioingenium.kbmed.text.representation.vo.Corpus;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver;
import com.thoughtworks.xstream.io.json.JsonWriter;
import gnu.trove.map.TObjectIntMap;
import gnu.trove.map.hash.TObjectIntHashMap;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Ing. Alejandro Riveros Cruz
 */
public class VisualizationService {

    private static final String TOP_TERM = "T";
    private static final String INDEX_TERM = "I";
    private static final String EXPANDED_TERM = "E";
    private static final String SURGERY_TERM = "S";
    /*
     *
     */
    private KnowledgeService knowledgeService;
    /*
     *
     */
    private XStream xstream;

    /*
     *
     */
    private static VisualizationService visualizationServiceInstance;

    /**
     *
     * @return
     */
    public static VisualizationService getInstance() {
        if (visualizationServiceInstance == null) {
            visualizationServiceInstance = new VisualizationService();
        }
        return visualizationServiceInstance;
    }

    private VisualizationService() {
        knowledgeService = KnowledgeService.getInstance();
        init();
    }

    private void init() {
        xstream = new XStream(new JsonHierarchicalStreamDriver() {

            @Override
            public HierarchicalStreamWriter createWriter(Writer writer) {
                return new JsonWriter(writer, JsonWriter.DROP_ROOT_MODE);
            }
        });
        xstream.setMode(XStream.NO_REFERENCES);
        xstream.alias(SubHierarchyNode.class.getSimpleName(), SubHierarchyNode.class);
        xstream.alias(NodeData.class.getSimpleName(), NodeData.class);
        xstream.aliasField("data", SubHierarchyNode.class, "term");
        xstream.aliasField("attr", SubHierarchyNode.class, "nodeData");
        xstream.omitField(SubHierarchyNode.class, "parent");
    }

    /**
     *
     * @param retrievalData
     * @param corpus
     * @return
     */
    public String getSubHierarchyJson(RetrievalData retrievalData, Corpus corpus) {
        String knowledgeSourceName = KnowledgeSourceMetaData.getReference().getName();
        String knowledgeSourceTopConceptId = KnowledgeSourceMetaData.getReference().getTopConceptId();
        //Build the inverted results map
        TObjectIntMap<String> inverterdResultsMap = new TObjectIntHashMap<String>();
        List<RelevantDocument> results = retrievalData.getResults();
        for (int i = 0; i < results.size(); i++) {
            inverterdResultsMap.put(results.get(i).getDocumentName(), i);
        }
        //Build subhierarchy
        Logger.getLogger(VisualizationService.class.getName()).log(Level.INFO, "Building the subhierarchy");
        SubHierarchy subHierarchy = buildUnifiedSubHierarchy(retrievalData, knowledgeSourceTopConceptId, knowledgeSourceName);
        //Build documentsByDescriptorIdIndex
        InvertedIndexStrToStr documentsByDescriptorIdIndex = buildDocumentsByDescriptorIdIndex(results, corpus);
        //Build parentsByChildrensIndex
        InvertedIndexStrToStr childrensByParentIndex = buildChildrensByParentIndex(subHierarchy);
        initSubHierarchyData(subHierarchy.getRoot(), inverterdResultsMap, documentsByDescriptorIdIndex, childrensByParentIndex);
        subHierarchy.fixMultipleHineritance();
        return getSubHierarchyJson(subHierarchy);

    }

    /**
     *
     * @param subHierarchy
     * @return
     */
    private String getSubHierarchyJson(SubHierarchy subHierarchy) {
        return xstream.toXML(subHierarchy.getRoot());
    }

    /**
     *
     * @param results
     * @param corpus
     * @return
     */
    private SubHierarchy buildUnifiedSubHierarchy(RetrievalData retrievalData, String knowledgeSourceTopConceptId, String knowledgeSourceName) {
        SubHierarchy subHierarchy = new SubHierarchy(knowledgeSourceName);
        subHierarchy.setRoot(knowledgeSourceTopConceptId, knowledgeSourceTopConceptId, "ROOT", knowledgeSourceName);
        subHierarchy.getRoot().getData().setNodeType(TOP_TERM);
        Set<String> candidateDescriptorSet = new HashSet<String>(retrievalData.getCandidateDescriptorSet());
        for (String descriptorId : candidateDescriptorSet) {
            createPathToRoot(subHierarchy, descriptorId);
            expandNodeChildrens(subHierarchy, descriptorId);
            expandNodeSiblings(subHierarchy, descriptorId);
            subHierarchy.getNode(knowledgeService.getConceptIdForDescriptorId(descriptorId)).getData().setNodeType(INDEX_TERM);
        }
        Set<String> expandedDescriptorSet = retrievalData.getExpandedDescriptorSet();
        for (String descriptorId : expandedDescriptorSet) {
            createPathToRoot(subHierarchy, descriptorId);
            subHierarchy.getNode(knowledgeService.getConceptIdForDescriptorId(descriptorId)).getData().setNodeType(EXPANDED_TERM);
        }
        return subHierarchy;
    }

    /**
     *
     * @param subHierarchy
     * @param descriptorId
     */
    private void createPathToRoot(SubHierarchy subHierarchy, String descriptorId) {
        List<String> pathToRootInConcepts = knowledgeService.getPathToRootByDescriptorId(descriptorId);
        List<String> pathToRootInDescriptors = knowledgeService.getDescriptorIdsFromConceptIds(pathToRootInConcepts);
        pathToRootInDescriptors.set(0, descriptorId);
        SubHierarchyNode currentNode = subHierarchy.getRoot();
        SubHierarchyNode subHierarchyNode;
        String tempDescriptorId, tempConceptId;
        for (int i = pathToRootInDescriptors.size() - 1; i >= 0; i--) {
            tempDescriptorId = pathToRootInDescriptors.get(i);
            tempConceptId = pathToRootInConcepts.get(i);
            subHierarchyNode = subHierarchy.getSubHierarchyNodeInstance(tempConceptId, tempDescriptorId, knowledgeService.getCategoryForConceptId(tempConceptId), knowledgeService.getTermForDescriptorId(tempDescriptorId));
            if (!subHierarchyNode.getData().isTypeAssigned()) {
                subHierarchyNode.getData().setNodeType(EXPANDED_TERM);
            }
            subHierarchy.addChildrenNode(currentNode.getData().getConceptId(), subHierarchyNode);
            currentNode = subHierarchyNode;
        }
    }

    /**
     *
     * @param subHierarchy
     * @param descriptorId
     */
    private void expandNodeChildrens(SubHierarchy subHierarchy, String descriptorId) {
        List<String> childrenConceptIds = new ArrayList<String>(knowledgeService.getDirectChildrensByDescriptorId(descriptorId));
        List<String> childrenDescriptorIds = knowledgeService.getDescriptorIdsFromConceptIds(childrenConceptIds);
        SubHierarchyNode subHierarchyNode;
        String tempDescriptorId, tempConceptId;
        for (int i = 0; i < childrenDescriptorIds.size(); i++) {
            tempDescriptorId = childrenDescriptorIds.get(i);
            tempConceptId = childrenConceptIds.get(i);
            subHierarchyNode = subHierarchy.getSubHierarchyNodeInstance(tempConceptId, tempDescriptorId, knowledgeService.getCategoryForConceptId(tempConceptId), knowledgeService.getTermForDescriptorId(tempDescriptorId));
            if (!subHierarchyNode.getData().isTypeAssigned()) {
                subHierarchyNode.getData().setNodeType(SURGERY_TERM);
            }
            subHierarchy.addChildrenNode(knowledgeService.getConceptIdForDescriptorId(descriptorId), subHierarchyNode);
        }
    }

    /**
     *
     * @param subHierarchy
     * @param descriptorId
     */
    private void expandNodeSiblings(SubHierarchy subHierarchy, String descriptorId) {
        List<String> parentConceptIds = new ArrayList<String>(knowledgeService.getDirectParentsByDescriptorId(descriptorId));
        List<String> parentDescriptorIds = knowledgeService.getDescriptorIdsFromConceptIds(parentConceptIds);
        for (String parentDescriptorId : parentDescriptorIds) {
            if (subHierarchy.containsNode(knowledgeService.getConceptIdForDescriptorId(parentDescriptorId))) {
                expandNodeChildrens(subHierarchy, parentDescriptorId);
            } else {
            }
        }
    }

    /**
     *
     * @param subHierarchy
     * @return
     */
    private InvertedIndexStrToStr buildChildrensByParentIndex(SubHierarchy subHierarchy) {
        InvertedIndexStrToStr parentsByChildrensIndex = new InvertedIndexStrToStr();
        buildChildrensByParentIndex(subHierarchy.getRoot(), parentsByChildrensIndex);
        return parentsByChildrensIndex;
    }

    /**
     *
     * @param subHierarchyNode
     * @param parentsByChildrensIndex
     * @return
     */
    private Set<String> buildChildrensByParentIndex(SubHierarchyNode subHierarchyNode, InvertedIndexStrToStr parentsByChildrensIndex) {
        parentsByChildrensIndex.addElement(subHierarchyNode.getData().getConceptId(), subHierarchyNode.getData().getConceptId());
        if (subHierarchyNode.getChildren().isEmpty()) {
            Set<String> tempSet = new HashSet<String>();
            tempSet.add(subHierarchyNode.getData().getConceptId());
            return tempSet;
        } else {
            Set<String> tempSet = new HashSet<String>();
            for (SubHierarchyNode childrenNode : subHierarchyNode.getChildren()) {
                tempSet.addAll(buildChildrensByParentIndex(childrenNode, parentsByChildrensIndex));
            }
            for (String childNodeId : tempSet) {
                parentsByChildrensIndex.addElement(subHierarchyNode.getData().getConceptId(), childNodeId);
            }
            return parentsByChildrensIndex.getElement(subHierarchyNode.getData().getConceptId());
        }
    }

    /**
     *
     * @param results
     * @param corpus
     * @return
     */
    private InvertedIndexStrToStr buildDocumentsByDescriptorIdIndex(List<RelevantDocument> results, Corpus corpus) {
        InvertedIndexStrToStr documentsByDescriptorIdIndex = new InvertedIndexStrToStr();
        Set<String> documentWords;
        for (RelevantDocument relevantDocument : results) {
            documentWords = corpus.getDocument(relevantDocument.getDocumentName()).getWords();
            for (String descriptorId : documentWords) {
                documentsByDescriptorIdIndex.addElement(knowledgeService.getConceptIdForDescriptorId(descriptorId), relevantDocument.getDocumentName());
            }
        }
        return documentsByDescriptorIdIndex;
    }

    /**
     *
     * @param subHierarchyNode
     * @param inverterdResultsMap
     * @param documentsByDescriptorIdIndex
     * @param childrensByParentIndex
     */
    private void initSubHierarchyData(SubHierarchyNode subHierarchyNode, TObjectIntMap<String> inverterdResultsMap, InvertedIndexStrToStr documentsByDescriptorIdIndex, InvertedIndexStrToStr childrensByParentIndex) {
        Set<String> childrenNodeIds = childrensByParentIndex.getElement(subHierarchyNode.getData().getConceptId());
        Set<String> documentIds = new HashSet<String>();
        Set<String> tempDocumentIds;
        for (String conceptId : childrenNodeIds) {
            if ((tempDocumentIds = documentsByDescriptorIdIndex.getElement(conceptId)) != null) {
                documentIds.addAll(tempDocumentIds);
            }
        }
        boolean[] tempArray = new boolean[inverterdResultsMap.size()];
        for (String documentId : documentIds) {
            tempArray[inverterdResultsMap.get(documentId)] = true;
        }
        StringBuilder sb = new StringBuilder();
        boolean last = true;
        int count = 0;
        for (int i = 0; i < tempArray.length; i++) {
            if (tempArray[i] != last) {
                last = !last;
                sb.append(String.valueOf(count)).append(",");
                count = 0;
            }
            count++;
        }
        subHierarchyNode.getData().setDocumentMap(sb.toString());
        for (SubHierarchyNode childrenNode : subHierarchyNode.getChildren()) {
            initSubHierarchyData(childrenNode, inverterdResultsMap, documentsByDescriptorIdIndex, childrensByParentIndex);
        }
    }

    public static void main(String[] args) {


        XStream xstream = new XStream(new JsonHierarchicalStreamDriver() {

            @Override
            public HierarchicalStreamWriter createWriter(Writer writer) {
                return new JsonWriter(writer, JsonWriter.DROP_ROOT_MODE);
            }
        });
        xstream.setMode(XStream.NO_REFERENCES);
        xstream.alias(SubHierarchyNode.class.getSimpleName(), SubHierarchyNode.class);
        xstream.omitField(SubHierarchyNode.class, "parent");
//        xstream.omitField(NodeData.class, "conceptId");
        //xstream.omitField(NodeData.class, "descriptorId");
        //xstream.omitField(NodeData.class, "category");
       // xstream.omitField(NodeData.class, "term");
        //xstream.omitField(NodeData.class, "documentMap");
        //xstream.omitField(NodeData.class, "nodeType");
        xstream.aliasField("data", SubHierarchyNode.class, "term");
        xstream.aliasField("attr", SubHierarchyNode.class, "nodeData");
        
        xstream.alias(NodeData.class.getSimpleName(), NodeData.class);





//        SubHierarchyNode root = new SubHierarchyNode("conceptRoot", "descriptorRoot", "category", "root");

//
////        node2L2.addChildren(node1L3);
//        node2L2.addChildren(node2L3);
//        node1L2.addChildren(node2L3);
//        node1L2.addChildren(node1L3);
////        node2L1.addChildren(node2L2);
//        node2L1.addChildren(node2L2);
////        node2L1.addChildren(node1L2);
//        node1L1.addChildren(node1L2);
//        root.addChildren(node2L1);
//        root.addChildren(node1L1);

        SubHierarchy subHierarchy = new SubHierarchy("test");
        subHierarchy.setRoot("root", "descriptorRoot", "category", "root");
        SubHierarchyNode root = subHierarchy.getRoot();
        root.getData().setNodeType(TOP_TERM);
        //Create nodes
        SubHierarchyNode node1L1 = subHierarchy.getSubHierarchyNodeInstance("node1L1", "descriptor1", "category", "1");
        SubHierarchyNode node2L1 = subHierarchy.getSubHierarchyNodeInstance("node2L1", "descriptor2", "category", "2");
        SubHierarchyNode node1L2 = subHierarchy.getSubHierarchyNodeInstance("node1L2", "descriptor3", "category", "3");
        SubHierarchyNode node2L2 = subHierarchy.getSubHierarchyNodeInstance("node2L2", "descriptor4", "category", "4");
        SubHierarchyNode node1L3 = subHierarchy.getSubHierarchyNodeInstance("node1L3", "descriptor5", "category", "5");
        SubHierarchyNode node2L3 = subHierarchy.getSubHierarchyNodeInstance("node2L3", "descriptor6", "category", "6");

        //add relationships
        subHierarchy.addChildrenNode("root", node1L1);
        subHierarchy.addChildrenNode("root", node2L1);
        subHierarchy.addChildrenNode("node1L1", node1L2);
//        sh.addChildrenNode("node1L1", node2L2);//
        subHierarchy.addChildrenNode("node1L1", node2L2);
        subHierarchy.addChildrenNode("node2L1", node2L2);
        subHierarchy.addChildrenNode("node1L2", node1L3);
//        sh.addChildrenNode("node1L2", node2L3);//
        subHierarchy.addChildrenNode("node2L2", node2L3);




//        List<String> nodeList = sh.getMultipleHineritanceNodes();
//        for (String nodeId : nodeList) {
//            System.out.println(nodeId);

        subHierarchy.fixMultipleHineritance();
        System.out.println(xstream.toXML(subHierarchy.getRoot()));

    }
}
